/*
 * SystemBuilder.cpp
 *
 *  Created on: 14 Jul 2011
 *      Author: al2010
 */

#include "SystemBuilder.h"

// C++ includes
#include <algorithm>

// Boost includes
#include <boost/foreach.hpp>

//GeoReact includes
#include "Database.h"
#include "Phase.h"
#include "Reaction.h"
#include "ReactionBuilder.h"
#include "System.h"

const System SystemBuilder::GenerateSystem(const StringVector& knownSpecies)
{
	StringVector allSpecies = GenerateSpecies(knownSpecies);
	
	const vector<Phase> phases = GeneratePhases(allSpecies);
	
	System system;
	
	system.SetPhases(phases);
	
	const vector<Reaction> reactions = GenerateReactions(system);
	
	system.SetReactions(reactions);
	
	return system;
}

const System SystemBuilder::GenerateSystemNoSpeciesGeneration(const StringVector& allSpecies)
{
	const vector<Phase> phases = GeneratePhases(allSpecies);
	
	System system;
	
	system.SetPhases(phases);
	
	const vector<Reaction> reactions = GenerateReactions(system);
	
	system.SetReactions(reactions);
	
	return system;
}

const StringVector SystemBuilder::FilterSpecies(const StringVector& species, const StringVector& tags)
{
	// The subset of the given species that have any of the given tags
	StringVector newSpecies;
	
	// Loop over all the given species 
	BOOST_FOREACH(const string& s, species)
		if(Database::GetInstance().GetSpeciesInfo(s).HasAnyTag(tags))
			newSpecies.push_back(s);
			
	return newSpecies;
}

const StringVector SystemBuilder::RemoveSpecies(const StringVector& species, const StringVector& tags)
{
	// The subset of the given species that does not have any of the given tags
	StringVector newSpecies;
	
	// Loop over all the given species 
	BOOST_FOREACH(const string& s, species)
		if(!Database::GetInstance().GetSpeciesInfo(s).HasAnyTag(tags))
			newSpecies.push_back(s);
			
	return newSpecies;
}

const StringVector SystemBuilder::GenerateSpecies(const StringVector& knownSpecies)
{
	const StringVector knownBasisSpecies     = FilterSpecies(knownSpecies, {"basis"});
	const StringVector knownSecondarySpecies = RemoveSpecies(knownSpecies, {"basis"});
	
	const StringVector allPossibleBasisSpecies = GenerateBasisSpecies(knownSecondarySpecies);
	
	set<String> setBasisSpecies, setSecondarySpecies;
	
	setBasisSpecies.insert("H2O");
	setBasisSpecies.insert("H[+]");
	setBasisSpecies.insert(knownBasisSpecies.begin(), knownBasisSpecies.end());
	setBasisSpecies.insert(allPossibleBasisSpecies.begin(), allPossibleBasisSpecies.end());
	
	const StringVector allPossibleSecondarySpecies        = GenerateSecondarySpecies( StringVector(setBasisSpecies.begin(), setBasisSpecies.end()) );
	const StringVector allPossibleAqueousSecondarySpecies = FilterSpecies(allPossibleSecondarySpecies, {"aqueous"});
	
	setSecondarySpecies.insert(knownSecondarySpecies.begin(), knownSecondarySpecies.end());
	setSecondarySpecies.insert(allPossibleAqueousSecondarySpecies.begin(), allPossibleAqueousSecondarySpecies.end());
	
	StringVector allSpecies;
	
	allSpecies.insert(allSpecies.end(), setBasisSpecies.begin(), setBasisSpecies.end());
	allSpecies.insert(allSpecies.end(), setSecondarySpecies.begin(), setSecondarySpecies.end());
	
	return allSpecies;
}

const StringVector SystemBuilder::GenerateBasisSpecies(const StringVector& secondarySpecies)
{
	// Get an instance of the database
	const Database& db = Database::GetInstance();
	
	// The set containing only unique basis species
	set<string> basisSpecies;
	
	// Loop over all the secondary species
	BOOST_FOREACH(const string& s, secondarySpecies)
	{
		// Get the info of the reaction in which the current secondary species is the reactant
		const ReactionInfo reactionInfo = db.GetReactionInfo(s);
	
		// Add all the products in the reaction to the set of basis species being created (all these products are basis species)
		basisSpecies.insert(reactionInfo.products.begin(), reactionInfo.products.end());
	}
	
	// Convert the set of basis species to a vector container and return it
	return StringVector(basisSpecies.begin(), basisSpecies.end());
}

const StringVector SystemBuilder::GenerateSecondarySpecies(const StringVector& basisSpecies)
{
	// Get an instance of the database
	const Database& db = Database::GetInstance();
	
	// Get a reference to the list of reactions in the database
	const Database::ReactionInfoList& reactionInfoList = db.GetReactionInfoList();
	
	// The vector of secondary species that can be built from the given set of basis species
	StringVector secondarySpecies;
	
	// Loop over all the reactions in the database
	BOOST_FOREACH(const ReactionInfo& r, reactionInfoList)
	{
		// An auxiliary boolean variable to determine if all the products of the current reaction belong to the set of basis species
		bool belong = true;
		
		//  Check if the products of the current reaction belong to the given set of basis species 
		BOOST_FOREACH(const string& product, r.products)
			if(!std::count(basisSpecies.begin(), basisSpecies.end(), product))
				belong = false;
		
		// Insert the reactant (a secondary species) of the current reaction into the vector of secondary species
		if(belong)
			secondarySpecies.push_back(r.reactant);
	}
	
	return secondarySpecies;
}

const vector<Phase> SystemBuilder::GeneratePhases(const StringVector& allSpecies)
{
	vector<Phase> phases;
	
	StringVector aqueousSpecies = FilterSpecies(allSpecies, {"aqueous"});
	StringVector gaseousSpecies = FilterSpecies(allSpecies, {"gaseous"});
	StringVector mineralSpecies = FilterSpecies(allSpecies, {"mineral"});
	
	if(!aqueousSpecies.empty())
		phases.push_back( Phase::CreateAqueousPhase(aqueousSpecies) );
	
	if(!gaseousSpecies.empty())
		phases.push_back( Phase::CreateGaseousPhase(gaseousSpecies) );
	
	BOOST_FOREACH(const String& mineral, mineralSpecies)
		phases.push_back( Phase::CreateMineralPhase({mineral}) );
	
	return phases;
}

const vector<Reaction> SystemBuilder::GenerateReactions(const System& system)
{
	// The reactions whose reactants are the given species
	vector<Reaction> reactions; reactions.reserve(system.GetSpecies().size());
	
	const SpeciesMap speciesMap(system.GetSpecies());
	
	// Loop over all the secondary species and create the appropriate reactions using the reaction builder methods
	BOOST_FOREACH(const String& speciesName, system.GetSpecies())
		reactions.push_back( ReactionBuilder::CreateReaction(speciesMap, speciesName) );
	
	return reactions;
}
